import {
    _decorator,
    Button,
    color,
    Component,
    EventHandle,
    EventHandler,
    EventMouse,
    EventTouch,
    gfx,
    Node,
    NodeEventType,
    Sprite,
    UITransform,
    v2,
    v3,
    Vec2,
} from 'cc';
const { ccclass, property, menu, requireComponent } = _decorator;

export enum ButtonState {
    NORMAL,
    HOVER,
    PRESSED,
    DISABLED,
}

@ccclass('PixelButton')
@menu('UI/PixelButton')
@requireComponent(UITransform)
export class PixelButton extends Component {
    static readonly EventStateChange = 'EventStateChange';

    @property([EventHandler])
    public clickEvents: EventHandler[] = [];

    @property(Sprite)
    debugTarget: Sprite = null!;

    @property
    get interactable(): boolean {
        return this._interactable;
    }
    set interactable(value) {
        if (this._interactable === value) {
            return;
        }
        this._interactable = value;
        this._updateState();
    }

    _canHover = true;
    @property
    get canHover(): boolean {
        return this._canHover;
    }
    set canHover(value) {
        this._canHover = value;
    }

    private _pos = v3();
    private _pos2 = v3();

    private _interactable = true;
    private _pressed = false;
    private _hovered = false;
    private _state = ButtonState.NORMAL;

    protected onLoad(): void {
        this.addEvent();
    }

    // protected update(dt: number): void {}

    protected onDestroy(): void {
        this.removeEvent();
    }

    protected _updateState(): void {
        const state = this._getButtonState();
        if (this._state !== state) {
            this._state = state;
            this.node.emit(
                PixelButton.EventStateChange,
                this._state,
                this,
            );
        }
    }

    protected _getButtonState(): ButtonState {
        let state = ButtonState.NORMAL;
        if (!this._interactable) {
            state = ButtonState.DISABLED;
        } else if (this._pressed) {
            state = ButtonState.PRESSED;
        } else if (this._hovered) {
            state = ButtonState.HOVER;
        }
        return state;
    }

    addEvent() {
        this.node.on(NodeEventType.TOUCH_START, this._onTouchBegan, this);
        this.node.on(NodeEventType.TOUCH_MOVE, this._onTouchMove, this);
        this.node.on(NodeEventType.TOUCH_END, this._onTouchEnded, this);
        this.node.on(
            NodeEventType.TOUCH_CANCEL,
            this._onTouchCancel,
            this,
        );

        if (this.canHover) {
            this.node.on(
                NodeEventType.MOUSE_MOVE,
                this._onMouseMove,
                this,
            );
            this.node.on(
                NodeEventType.MOUSE_LEAVE,
                this._onMouseLevel,
                this,
            );
        }
    }

    removeEvent() {
        this.node.off(NodeEventType.TOUCH_START, this._onTouchBegan, this);
        this.node.off(NodeEventType.TOUCH_MOVE, this._onTouchMove, this);
        this.node.off(NodeEventType.TOUCH_END, this._onTouchEnded, this);
        this.node.off(
            NodeEventType.TOUCH_CANCEL,
            this._onTouchCancel,
            this,
        );

        this.node.off(NodeEventType.MOUSE_ENTER, this._onMouseMove, this);
        this.node.off(NodeEventType.MOUSE_LEAVE, this._onMouseLevel, this);
    }

    _onTouchBegan(event: EventTouch) {
        if (!this._interactable || !this.enabledInHierarchy) {
            return;
        }
        if (!this._testGetColor(event.getUILocation())) {
            return;
        }
        this._pressed = true;
        this._updateState();
        event.propagationStopped = true;
    }

    _onTouchMove(event?: EventTouch): void {
        const uiTransformComp = this.node.getComponent(UITransform);

        const hit =
            uiTransformComp.hitTest(
                event.touch.getLocation(),
                event.windowId,
            ) && this._testGetColor(event.getUILocation());

        let state: ButtonState;
        if (hit) {
            state = ButtonState.PRESSED;
            event.propagationStopped = true;
        } else {
            state = ButtonState.NORMAL;
        }
    }

    _onTouchEnded(event?: EventTouch): void {
        if (!this._interactable || !this.enabledInHierarchy) {
            return;
        }
        if (!this._testGetColor(event.getUILocation())) {
            return;
        }

        if (this._pressed) {
            EventHandler.emitEvents(this.clickEvents, event);
            this.node.emit(Button.EventType.CLICK, this);
        }
        this._pressed = false;
        this._updateState();

        if (event) {
            event.propagationStopped = true;
        }
    }

    _onTouchCancel(event?: EventTouch): void {
        if (!this._interactable || !this.enabledInHierarchy) {
            return;
        }
        this._pressed = false;
        this._updateState();
    }

    _onMouseMove(event?: EventMouse) {
        // console.log('onMouseMove', event.getLocation().toString());
        if (!this._interactable || !this.enabledInHierarchy) {
            return;
        }
        if (!this._testGetColor(event.getUILocation())) {
            this._onMouseLevel();
            return;
        }
        this._hovered = true;
        this._updateState();
    }

    _onMouseLevel(event?: EventMouse) {
        if (!this._interactable || !this.enabledInHierarchy) {
            return;
        }
        this._hovered = false;
        this._updateState();
    }

    _testGetColor(ui_pos: Vec2) {
        const sprite = this.node.getComponent(Sprite);
        const ui = this.node.getComponent(UITransform);
        ui.convertToNodeSpaceAR(v3(ui_pos.x, ui_pos.y, 0), this._pos2);
        const index = _getBufferIndex(
            v2(this._pos2.x, this._pos2.y),
            sprite,
        );

        const col = _checkPixels(v2(this._pos2.x, this._pos2.y), sprite);
        if (this.debugTarget) {
            console.log(this.debugTarget.color.toString());
            this.debugTarget.color = col;
        }
        return col.a > 255 * 0.25;
    }
}

// 检查像素
function _checkPixels(position: Vec2, sprite: Sprite) {
    let buffer = readPixelsFromSprite(sprite);
    let index = _getBufferIndex(position, sprite);

    return color(
        buffer[index + 0],
        buffer[index + 1],
        buffer[index + 2],
        buffer[index + 3],
    );
}

/**
 * 读取Sprite像素
 * 返回 buffer对象 , buffer对象格式
 * buffer[i*4+0] r
 * buffer[i*4+1] g
 * buffer[i*4+2] b
 * buffer[i*4+3] a
 *
 */
function readPixelsFromSprite(sprite: Sprite) {
    let buffer: Uint8Array = null;

    let spriteFrame = sprite.spriteFrame;
    let texture = spriteFrame.texture;
    let tx = spriteFrame.rect.x;
    let ty = spriteFrame.rect.y;
    if (spriteFrame.packable && spriteFrame.original) {
        texture = spriteFrame.original._texture;
        tx = spriteFrame.original._x;
        ty = spriteFrame.original._y;
    }
    let width = spriteFrame.rect.width;
    let height = spriteFrame.rect.height;

    let gfxTexture = texture.getGFXTexture();
    let gfxDevice = texture['_getGFXDevice']();
    let bufferViews = [];
    let region = new gfx.BufferTextureCopy();
    buffer = new Uint8Array(width * height * 4);
    (region.texOffset.x = tx), (region.texOffset.y = ty);
    region.texExtent.width = width;
    region.texExtent.height = height;
    bufferViews.push(buffer);
    gfxDevice?.copyTextureToBuffers(gfxTexture, bufferViews, [region]);
    return buffer;
}

function _getBufferIndex(position: Vec2, sprite: Sprite) {
    const spriteFrame = sprite.spriteFrame;
    const texWidth = spriteFrame.rect.width;
    const texHeight = spriteFrame.rect.height;
    const originSize = spriteFrame.originalSize;
    const uiTrans = sprite.getComponent(UITransform)!;

    const anchorX = uiTrans.anchorX;
    const anchorY = uiTrans.anchorY;

    const contentWidth = uiTrans.width;
    const contentHeight = uiTrans.height;

    let index = -1;

    if (sprite.trim) {
        let x = Math.floor(
            position.x / (contentWidth / texWidth) + texWidth * anchorX,
        );
        let y = Math.floor(
            texHeight -
                (position.y / (contentHeight / texHeight) +
                    texHeight * anchorY),
        );
        index = (y * texWidth + x) * 4;
    } else {
        let scaleX = contentWidth / originSize.width; // 计算原始图像与节点大小的缩放系数
        let scaleY = contentHeight / originSize.height;

        let leftPoint = position.x + contentWidth * anchorX; // 转换到左上角坐标
        let topPoint = Math.abs(
            position.y + contentHeight * (anchorY - 1),
        );

        let tx = spriteFrame.rect.x;
        let ty = spriteFrame.rect.y;
        if (spriteFrame.packable && spriteFrame.original) {
            tx = spriteFrame.original._x;
            ty = spriteFrame.original._y;
        }
        // 计算鼠标在图像像素上的位置
        let x = Math.floor((leftPoint - tx * scaleX) / scaleX);
        let y = Math.floor((topPoint - ty * scaleY) / scaleY);
        index = (y * texWidth + x) * 4;
    }

    return index;
}
